using System;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;

using SilverMenu;
using TInput = System.Xml.XmlDocument;
using TOutput = SilverMenu.SilverMenu;

namespace SilverMenuPipeline
{
	/// <summary>
	/// This class will be instantiated by the XNA Framework Content Pipeline
	/// to apply custom processing to content data, converting an object of
	/// type TInput to TOutput. The input and output types may be the same if
	/// the processor wishes to alter data without changing its type.
	///
	/// This should be part of a Content Pipeline Extension Library project.
	///
	/// </summary>
	[ContentProcessor(DisplayName = "SilverMenu Processor")]
	public class SilverMenuProcessor : ContentProcessor<TInput, TOutput>
	{
		public override TOutput Process(TInput input, ContentProcessorContext context)
		{
			TOutput output = new TOutput();

			//Debugger.Launch();
			if (input.GetElementsByTagName("SilverMenu").Count > 0)
			{
				foreach (XmlNode node in input.GetElementsByTagName("SilverMenu")[0].ChildNodes)
				{
					Page page = null;
					switch (node.Name)
					{
						case "Version":
							output.Version = Double.Parse(node.InnerText, CultureInfo.InvariantCulture.NumberFormat);
							break;

						case "Page":
							page = new Page();
							foreach (XmlAttribute attr in node.Attributes)
							{
								switch (attr.Name)
								{
									case "id":
										page.Name = attr.Value;
										break;
								}
							}

							if (page.Name == "")
								throw new XmlException("Page node must have an id attribute");

							foreach (XmlNode prop in node.ChildNodes)
							{
								switch(prop.Name)
								{
									case "TransitionTime":
										page.TransitionTime = float.Parse(prop.InnerText, CultureInfo.InvariantCulture.NumberFormat);
										break;

									case "Flick":
										if(prop.Attributes == null || prop.Attributes.Count != 1)
											throw new XmlException("Flick node must only have one attribute named type.");

										if(prop.Attributes[0].Name != "type")
											throw new XmlException("Invalid attribute name " + prop.Attributes[0] + ". Name should be: type.");

										string flicktype = prop.Attributes[0].Value;
										FlickTypes ft;
										if(!Enum.TryParse(flicktype, true, out ft))
											throw new XmlException("Invalid flick type: " + flicktype + "!");

										page.Flicks[(int)ft] = prop.InnerText;
										break;

									case "Elements":
										ReadElements(prop, ref page);
										break;
								}
							}
							output.Pages.Add(page.Name, page);

							break;
					}
				}
			}

			return output;
		}

		/// <summary>
		/// Loads all the elements of a page.
		/// </summary>
		/// <param name="node">The current XML node to parse.</param>
		/// <param name="page">The page that holds the elements that will be loaded.</param>
		private void ReadElements(XmlNode node, ref Page page)
		{
			GUIElement elem = null;

			foreach (XmlNode elemNode in node.ChildNodes)
			{

				switch (elemNode.Name)
				{
					case "Background":
						elem = ReadBackground(elemNode);
						break;
					case "Button":
						elem = ReadButton(elemNode);
						break;
					case "Text":
						elem = ReadText(elemNode);
						break;
				}

				if (elem != null)
				{
					if (!elem.HasTransitionTime)
						elem.TransitionTime = page.TransitionTime;

					page.Elements.Add(elem);
				}
			}
		}

		/// <summary>
		/// Load common attributes of a GUI element.
		/// </summary>
		/// <param name="elem">GUI Element that is being loaded.</param>
		/// <param name="node">The current XML node to parse.</param>
		private void ReadElement(GUIElement elem, XmlNode node)
		{
			// First, take care of attributes.
			foreach (XmlAttribute attr in node.Attributes)
			{
				switch (attr.Name)
				{
					case "id":
						elem.Name = attr.Value;
						break;
				}
			}

			if(elem.Name == "")
				throw new XmlException("GUIElement node must have an id attribute");

			// Then we parse the child nodes to read the rest of the properties.
			//NOTE: There should be only a rect or a position per element.
			foreach (XmlNode prop in node.ChildNodes)
			{
				String[] vals;
				switch (prop.Name)
				{
					case "Rect":
						vals = prop.InnerText.Split(' ');
						try
						{
							int x = int.Parse(vals[0], CultureInfo.InvariantCulture.NumberFormat);
							int y = int.Parse(vals[1], CultureInfo.InvariantCulture.NumberFormat);
							
							Rectangle r = new Rectangle(
								x,
								y,
								int.Parse(vals[2], CultureInfo.InvariantCulture.NumberFormat),
								int.Parse(vals[3], CultureInfo.InvariantCulture.NumberFormat));

							elem.Rect = r;
							elem.Position = new Vector2(x, y);
						}
						catch
						{
							throw new FormatException("Input string " + prop.InnerText + " is not in a correct format");
						}
						break;

					//TODO: If we use this property on a gui element that needs to have a width and height, it won't work correctly.
					case "Position":
						vals = prop.InnerText.Split(' ');
						try
						{
							int x = int.Parse(vals[0], CultureInfo.InvariantCulture.NumberFormat);
							int y = int.Parse(vals[1], CultureInfo.InvariantCulture.NumberFormat);

							Vector2 v = new Vector2(x, y);
							elem.Position = v;
							elem.Rect = new Rectangle(x, y, 0, 0);
						}
						catch
						{
							throw new FormatException("Input string " + prop.InnerText + " is not in a correct format");
						}
						break;

					case "TransitionTime":
						elem.TransitionTime = float.Parse(prop.InnerText, CultureInfo.InvariantCulture.NumberFormat);
						elem.HasTransitionTime = true;
						break;

					case "Color":
						vals = prop.InnerText.Split(' ');
						try
						{
							int r = int.Parse(vals[0], CultureInfo.InvariantCulture.NumberFormat);
                            int g = int.Parse(vals[1], CultureInfo.InvariantCulture.NumberFormat);
                            int b = int.Parse(vals[2], CultureInfo.InvariantCulture.NumberFormat);
                            int a = int.Parse(vals[3], CultureInfo.InvariantCulture.NumberFormat);

						    elem.Color = Color.FromNonPremultiplied(r, g, b, a);
						}
						catch
						{
							throw new FormatException("Input string " + prop.InnerText + " is not in a correct format");
						}
						break;
				}
			}
		}

		/// <summary>
		/// Loads a background gui element.
		/// </summary>
		/// <param name="node">The current XML node to parse.</param>
		/// <returns>A SilverBackground gui element.</returns>
		private SilverBackground ReadBackground(XmlNode node)
		{
			SilverBackground bg = new SilverBackground();

			ReadElement(bg, node);

			foreach (XmlNode prop in node.ChildNodes)
			{
				switch (prop.Name)
				{
					case "Source":
						bg.Source = prop.InnerText;
						break;
				}
			}


			return bg;
		}

		/// <summary>
		/// Loads a button gui element.
		/// </summary>
		/// <param name="node">The current XML node to parse.</param>
		/// <returns>A SilverButton gui element.</returns>
		private SilverButton ReadButton(XmlNode node)
		{
			SilverButton btn = new SilverButton();

			ReadElement(btn, node);
            //System.Diagnostics.Debugger.Launch();

		    String[] vals;
			foreach (XmlNode prop in node.ChildNodes)
			{
			    switch (prop.Name)
			    {
			        case "Source":
			            btn.Source = prop.InnerText;
			            break;
			        case "Font":
			            btn.FontSource = prop.InnerText;
			            break;
			        case "Text":
			            btn.Text = prop.InnerText;
			            break;
			        case "TextColor":
			            vals = prop.InnerText.Split(' ');
			            try
			            {
			                int r = int.Parse(vals[0], CultureInfo.InvariantCulture.NumberFormat);
			                int g = int.Parse(vals[1], CultureInfo.InvariantCulture.NumberFormat);
			                int b = int.Parse(vals[2], CultureInfo.InvariantCulture.NumberFormat);
			                int a = int.Parse(vals[3], CultureInfo.InvariantCulture.NumberFormat);

			                btn.TextColor = Color.FromNonPremultiplied(r, g, b, a);
			            }
			            catch
			            {
			                throw new FormatException("Input string " + prop.InnerText + " is not in a correct format");
			            }
			            break;
			        case "OnClick":
			            if (prop.Attributes == null || prop.Attributes.Count != 1)
			                throw new XmlException("OnClick must only have one attribute named type.");

			            if (prop.Attributes[0].Name != "type")
			                throw new XmlException("Invalid attribute name " + prop.Attributes[0] + ". Name should be: type.");

			            String type = prop.Attributes[0].Value;
			            if (type.Equals("changepage", StringComparison.InvariantCultureIgnoreCase))
			                btn.NextPage = prop.InnerText;
			            else if (type.Equals("eventhandler", StringComparison.InvariantCultureIgnoreCase))
			            {
			                btn.EventHandlerName = prop.InnerText;
			            }

			            break;

			        case "Offset":
			            vals = prop.InnerText.Split(' ');
			            try
			            {
			                int x = int.Parse(vals[0], CultureInfo.InvariantCulture.NumberFormat);
			                int y = int.Parse(vals[1], CultureInfo.InvariantCulture.NumberFormat);

			                btn.Offset = new Vector2(x, y);
			            }
			            catch
			            {
			                throw new FormatException("Input string " + prop.InnerText + " is not in a correct format");
			            }

			            break;
			    }
			}

			return btn;
		}

		/// <summary>
		/// Loads a text gui element.
		/// </summary>
		/// <param name="node">The current XML node to parse.</param>
		/// <returns>A SilverText gui element.</returns>
		private SilverText ReadText(XmlNode node)
		{
			SilverText st = new SilverText();

			ReadElement(st, node);

			foreach (XmlNode prop in node.ChildNodes)
			{
				switch (prop.Name)
				{
					case "Font":
						st.Source = prop.InnerText;
						break;
					case "Text":
						st.Text = prop.InnerText;
						break;
				}
			}

			return st;
		}
	}
}